001    package net.sf.xdc.processing;
002    
003    /*
004     *  Copyright 2005-2006 Jens Voß.
005     *
006     *  Licensed under the GNU Lesser General Public License (the "License");
007     *  you may not use this file except in compliance with the License.
008     *  You may obtain a copy of the License at
009     *
010     *       http://opensource.org/licenses/lgpl-license.php
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    
019    import java.io.File;
020    import java.io.FileNotFoundException;
021    import java.io.FileReader;
022    import java.io.IOException;
023    import java.io.Reader;
024    import java.io.StringWriter;
025    import java.util.Arrays;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.Set;
029    import java.util.SortedMap;
030    import java.util.SortedSet;
031    import java.util.StringTokenizer;
032    import java.util.TreeMap;
033    import java.util.TreeSet;
034    
035    import net.sf.xdc.util.IOUtils;
036    import net.sf.xdc.util.Logging;
037    import net.sf.xdc.util.PathDescriptor;
038    import org.apache.commons.cli.CommandLine;
039    import org.apache.log4j.Logger;
040    
041    /**
042     * The <code>XdcSourceCollector</code> class is used to assemble the possible
043     * XML source files specified for processing into XDC documentation.
044     *
045     * @author Jens Voß
046     * @since 0.5
047     * @version 0.5
048     */
049    public class XdcSourceCollector {
050    
051      private static final Logger LOG = Logging.getLogger();
052    
053      private static final String[] EMPTY_STRING_ARRAY = new String[0];
054    
055      private CommandLine commandLine;
056      private String[] sourcePaths;
057      private File[] sourceDirs;
058      private String[] subpackages;
059      private String[] excluded;
060      private String[] filePaths;
061      private boolean defaultExcludes;
062      private SortedSet xdcSources = new TreeSet(); // SortedSet<XdcSource>
063      private SortedMap xdcPackages = new TreeMap(); // SortedMap<String, XdcPackage>
064    
065      private FileAction addAction = new FileAction() {
066        public void execute(XdcSource xdcSource) {
067          XdcSourceCollector.this.xdcSources.add(xdcSource);
068          String packageName = xdcSource.getPackageName();
069          XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
070          if (xdcPackage == null) {
071            xdcPackage = new XdcPackage(packageName);
072            xdcPackages.put(packageName, xdcPackage);
073          }
074          xdcPackage.addSource(xdcSource);
075        }
076      };
077    
078      private FileAction removeAction = new FileAction() {
079        public void execute(XdcSource xdcSource) {
080          XdcSourceCollector.this.xdcSources.remove(xdcSource);
081          String packageName = xdcSource.getPackageName();
082          XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
083          xdcPackage.removeSource(xdcSource);
084        }
085      };
086    
087      /**
088       * Public constructor.
089       * @param commandLine The <code>CommandLine</code> containing all option
090       *        values of the XDC invocation
091       */
092      public XdcSourceCollector(CommandLine commandLine) {
093        this.commandLine = commandLine;
094        this.sourcePaths = getStringArrayFromOption("sourcepath", false,
095                                                    new String[] {"."}, ";");
096        this.subpackages = getStringArrayFromOption("subpackages", true,
097                                                    EMPTY_STRING_ARRAY, ":");
098        this.excluded = getStringArrayFromOption("exclude", true,
099                                                 EMPTY_STRING_ARRAY, ":");
100        this.filePaths = commandLine.getArgs();
101        for (int i = 0; i < filePaths.length; i++) {
102          filePaths[i] = filePaths[i].replace('\\', '/');
103        }
104        defaultExcludes = commandLine.hasOption("defaultexcludes");
105        collectSourceDirs();
106        collectSubpackages();
107        collectArgumentFiles();
108      }
109    
110      /**
111       * This getter method retrieves all assembled XML source files.
112       *
113       * @return All XML source files specified for processing (in the form of an
114       *         array of <code>XdcSource</code> objects)
115       */
116      public XdcSource[] getXdcSources() {
117        return (XdcSource[]) xdcSources.toArray(new XdcSource[xdcSources.size()]);
118      }
119    
120      /**
121       * This method retrieves an {@link XdcPackage} object specified for processing
122       * by the XDC tool.
123       *
124       * @param packageName The name of the package
125       * @return The <code>XdcPackage</code> with the specified name
126       */
127      public XdcPackage getXdcPackage(String packageName) {
128        return (XdcPackage) xdcPackages.get(packageName);
129      }
130    
131      /**
132       * This method retrieves all <code>XdcSource</code> objects contained in an
133       * <code>XdcPackage</code>.
134       *
135       * @param packageName The name of the package from which the sources are
136       *        returned
137       * @return All XML source files (in the form of an array of
138       *         <code>XdcSource</code> objects) contained in the package with the
139       *         specified name.
140       */
141      public XdcSource[] getXdcSources(String packageName) {
142        XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
143        return xdcPackage.getXdcSources();
144      }
145    
146      /**
147       * This method retrieves a particular <code>XdcSource</code> by its
148       * (fully-qualified) source name.
149       *
150       * @param sourceName The fully-qualified name of the <code>XdcSource</code>
151       *        to be retrieved
152       * @return The <code>XdcSource</code> with the given name
153       */
154      public XdcSource getXdcSource(String sourceName) {
155        int pos = sourceName.lastIndexOf('/');
156        String packageName = pos >= 0 ? sourceName.substring(0, pos) : "";
157        XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
158        return xdcPackage != null ? xdcPackage.getXdcSource(sourceName.substring(pos + 1)) : null;
159      }
160    
161      private String[] getStringArrayFromOption(String option, boolean replacePeriod,
162                                                String[] defaultValue, String delim) {
163        if (commandLine.hasOption(option)) {
164          String spVal = commandLine.getOptionValue(option);
165          StringTokenizer tok = new StringTokenizer(spVal, delim, false);
166          Set sps = new HashSet();
167          while (tok.hasMoreTokens()) {
168            String nextElem = tok.nextToken().replace('\\', '/');
169            if (replacePeriod) {
170              nextElem = nextElem.replace('.', '/');
171            }
172            sps.add(nextElem);
173          }
174          return (String[]) sps.toArray(new String[sps.size()]);
175        }
176        else {
177          return defaultValue;
178        }
179      }
180    
181      private void collectSourceDirs() {
182        Set dirs = new HashSet(sourcePaths.length);
183        for (Iterator iter = Arrays.asList(sourcePaths).iterator(); iter.hasNext();) {
184          String path = (String) iter.next();
185          File dir;
186          try {
187            dir = new File(path).getCanonicalFile();
188          }
189          catch (IOException e) {
190            LOG.error(e.getMessage(), e);
191            continue;
192          }
193          if (!dir.exists()) {
194            LOG.warn("Source path " + path + " does not exist.");
195          }
196          else if (!dir.isDirectory()) {
197            LOG.warn("Source path " + path + " is not a directory.");
198          }
199          else {
200            dirs.add(dir);
201          }
202        }
203        this.sourceDirs = (File[]) dirs.toArray(new File[dirs.size()]);
204      }
205    
206      private void collectSubpackages() {
207        Set extensions = new HashSet();
208        if (commandLine.hasOption("extensions")) {
209          StringTokenizer tok = new StringTokenizer(commandLine.getOptionValue("extensions"), ",", false);
210          while (tok.hasMoreTokens()) {
211            extensions.add(tok.nextToken());
212          }
213        }
214        // add files specified by the "-subpackages" option ...
215        for (int i = 0; i < sourceDirs.length; i++) {
216          File sourceDir = sourceDirs[i];
217          for (int j = 0; j < subpackages.length; j++) {
218            String subpackage = subpackages[j];
219            File pkg = new File(sourceDir, subpackage);
220            if (pkg.exists() && pkg.isDirectory()) {
221              recurse(new FileSelector(pkg, extensions, defaultExcludes), subpackage, addAction, sourceDir);
222            }
223          }
224        }
225        // ... and then remove thos specified by the "-exclude" option
226        for (int i = 0; i < sourceDirs.length; i++) {
227          File sourceDir = sourceDirs[i];
228          for (int j = 0; j < excluded.length; j++) {
229            String excludedPkg = excluded[j];
230            File pkg = new File(sourceDir, excludedPkg);
231            if (pkg.exists() && pkg.isDirectory()) {
232              recurse(new FileSelector(pkg, extensions, commandLine.hasOption("defaultexcludes")), excludedPkg, removeAction, sourceDir);
233            }
234          }
235        }
236      }
237    
238      private void collectArgumentFiles() {
239        PathDescriptor[] descriptors = new PathDescriptor[filePaths.length];
240        for (int i = 0; i < filePaths.length; i++) {
241          String filePath = filePaths[i];
242          descriptors[i] = new PathDescriptor(filePath, defaultExcludes);
243        }
244        for (int i = 0; i < sourceDirs.length; i++) {
245          File sourceDir = sourceDirs[i];
246          FileSelector selector = new PatternSelector(sourceDir, descriptors);
247          recurse(selector, "", addAction, sourceDir);
248        }
249      }
250    
251      private void recurse(FileSelector selector, String packageName,
252                           FileAction fileAction,
253                           File sourceDir) {
254        File[] files = selector.selectFiles();
255        for (int i = 0; i < files.length; i++) {
256          File file = files[i];
257          if (file.isFile()) {
258            DialectHandler handler = DialectHandler.getDialectHandler(file, packageName, commandLine);
259            fileAction.execute(new XdcSource(file, sourceDir, packageName, handler));
260          }
261          else if (file.isDirectory()) {
262            String filename = file.getName();
263            // jv, 05-Jul-2006: bug #1517436
264            String subPackageName = packageName != null && packageName.length() > 0 ? packageName + '/' + filename : filename;
265            recurse(selector.moveToSubdir(filename), subPackageName, fileAction,
266                    sourceDir);
267          }
268        }
269      }
270    
271      /**
272       * This method returns the directory specified as the first argument of the
273       * <code>-sourcepath</code> option. (This value is used as the root directory
274       * of the XDC output if no <code>-d</code> option is specified.)
275       *
276       * @return The <code>File</code> specified as the first sourcepath
277       */
278      public File getLeadingSourcePath() {
279        return this.sourceDirs[0];
280      }
281    
282      /**
283       * This method returns an array of the names of all packages containing
284       * XML source files specified for processing.
285       *
286       * @return The names of all collected <code>XdcPackages</code>
287       */
288      public String[] getPackageNames() {
289        Set retVal = xdcPackages.keySet();
290        return (String[]) retVal.toArray(new String[retVal.size()]);
291      }
292    
293      /**
294       * This method is used to determine the number of frames contained in the
295       * main frameset of the generated XDC documentation.
296       *
297       * @return If sources from just one <code>XdcPackage</code> have been
298       *         assembled, the method returns 2; otherwise it returns 3.
299       */
300      public int getFramesetSize() {
301        if (xdcPackages.size() == 1) {
302          return 2;
303        }
304        else {
305          return 3;
306        }
307      }
308    
309      /**
310       * This method attempts to extract text from an overview file (which is
311       * specified by the <code>-overview</code> option and must be placed in of the
312       * root directories specified as arguments to the <code>-sourcepath</code>
313       * options).
314       *
315       * @return All text placed between the opening and the closing <body>
316       *         tags of the overview file. Note that this text is returned "raw",
317       *         i.e. no XDC tags are processed yet.
318       */
319      public String getSummaryText() {
320        if (!commandLine.hasOption("overview")) {
321          return "";
322        }
323        String overview = commandLine.getOptionValue("overview");
324        for (int i = 0; i < sourceDirs.length; i++) {
325          File packageFile = new File(sourceDirs[i], overview);
326          if (packageFile.exists()) {
327            String retVal = getSummaryText(packageFile);
328            if (retVal != null) {
329              return retVal;
330            }
331          }
332        }
333        File packageFile = new File(overview);
334        if (packageFile.exists()) {
335          String retVal = getSummaryText(packageFile);
336          if (retVal != null) {
337            return retVal;
338          }
339        }
340        return "";
341      }
342    
343      private static String getSummaryText(File packageFile) {
344        String retVal = null;
345        Reader in = null;
346        try {
347          in = new FileReader(packageFile);
348          StringWriter out = new StringWriter();
349          IOUtils.copy(in, out);
350          StringBuffer buf = out.getBuffer();
351          int pos1 = buf.indexOf("<body>");
352          int pos2 = buf.lastIndexOf("</body>");
353          if (pos1 >= 0 && pos1 < pos2) {
354            retVal = buf.substring(pos1 + 6, pos2);
355          }
356          else {
357            retVal = "";
358          }
359        }
360        catch (FileNotFoundException e) {
361          LOG.error(e.getMessage(), e);
362        }
363        catch (IOException e) {
364          LOG.error(e.getMessage(), e);
365        }
366        finally {
367          if (in != null) {
368            try {
369              in.close();
370            }
371            catch (IOException e) {
372              LOG.error(e.getMessage(), e);
373            }
374          }
375        }
376        return retVal;
377      }
378    
379    }